package DHCPserver;

use strict;

use NetAddr::IP;

use Host;
use Utils;

use Data::Dumper;

#use Class::DBI::Plugin::RetrieveAll;

use base qw (NetworkDB);

__PACKAGE__->set_up_table('dhcp_servers');
__PACKAGE__->has_many(segments => 'Segment');

# this is called from parent class
sub initialize {
	my $self = shift;
	warn "Nacitavam dhcpd.leases . . .\n"; my $time_before = time;
	$self->loadLeases;
	warn "Nacitavanie dhcpd.leases skoncene. Operacia trvala ".(time - $time_before)." sekund.\n";
}

sub command {
        my $self = shift;
        my ($command) = @_;
        my $user = $self->default_user;
        my $IP = $self->fqdn;
#       print "sudo -u $user ssh $user\@$IP '$command'";
        my @output_lines = `sudo -u $user ssh $user\@$IP '$command'`;
        return @output_lines;
}

sub send_file {
	my $self = shift;
	my ($src_local_file, $dst_remote_file) = @_;
	my $user = $self->default_user;
	my $IP = $self->fqdn;
	my @output_lines = `sudo -u $user scp $src_local_file $user\@$IP\:$dst_remote_file`;
	return @output_lines;
}



sub IP2MAC {
        my $self = shift;
        my $IP = NetAddr::IP->new(shift);

        my $MAC = Host->IP2MAC($IP->addr); if ($MAC) { return ($MAC); }		# get IP addr. from db
	my @MACs = @{$self->IP2leasedMAC($IP->addr)};			# get IP addr. from dhcpd.leases
#	unshift @MACs, 'dynamic';
	# print Dumper @MACs; 
	return @MACs;
	# koniec je nedefinovany, ak pre danu IP nie je MAC
}

sub IP2leasedMAC {
	my $self = shift;
	my $IP = shift;
	return $self->{'{reverse_leases}'}->{$IP} || [0];	# pripad, ze by pole malo viac ako 1 prvok by nemal nastat
}

sub MAC2IP {
# TODO: urobit checkovanie spravnosti, lebo dhcpd.leases je niekedy nejednoznacny
	my $self = shift;
	my ($MAC, $VLAN) = @_;
	my $segment;
	if (defined $VLAN) {
		$segment = Segment->retrieve($VLAN);
	}
	my @IPs;
	my @IP = Host->MAC2IP($MAC);	# zistenie vsetkych moznych IP pre danu MAC z databazy
	foreach my $IP (@IP) {
		push @IPs, $IP->ip;	# naplnenie prvej casti pola IP adries
#		warn join '1', @IPs;
	}

	@IP = @{$self->MAC2leasedIP($MAC)}; # zistenie vsetkych moznych IP pre danu MAC z prislusneho dhcpd.leases 
	foreach my $IP (@IP) {
		if (defined $IP && $IP) {
			push @IPs, $IP;		# dokoncenie pola IP adries
		}
#		warn join '2', @IPs;
	}
	my @segmentIPs; 
	if (defined $segment) {
		foreach my $IP (@IPs) {
#			warn "\nIP = $IP\n";
			if (Network->IP2Segment($IP)->segmentid == $VLAN) {
				push @segmentIPs, $IP;		
			}
		}
		@IPs = @segmentIPs;
	}
	return @IPs;
	# koniec je nedefinovany ak pre danu MAC nie je IP
}

sub MAC2leasedIP {
	my $self = shift;
	my $MAC = shift;
	return $self->{'{leases}'}->{lc($MAC)} || [0];	# pozor! co ak nastane pripad, ze pre danu MAC bude viac IP?
}

sub loadLeases {
        my $self = shift;

	my $user = $self->default_user;
	my $dhcp_server_host = $self->fqdn;
	my $leases_file = $self->leases_file_path;
	# dhcpd.leases -> array
#        open (LEASES, "<".$self->locally_mounted_leases_file_path) || die "Could not open LEASES file!";
	my @individualLines = `sudo -u $user ssh $user\@$dhcp_server_host 'cat $leases_file'`;
#        my @individualLines = <LEASES>;
#        close(LEASES);
	
	my $leases; my $reverse_leases; my $MAC; my $state; my $strange_leases;
        while (my $line = pop @individualLines) {	# pop every line
                if ($line =~ /lease (.*) {/) {		# parse lease - read third
			if ($state eq 'active') {
				# if active, load into the object
				if (!Utils::is_in_array($MAC, @{$reverse_leases->{$1}})) {	# ak este nie je loaded
		                        push @{$reverse_leases->{$1}}, $MAC;
				}
				if (!Utils::is_in_array($1, @{$leases->{$MAC}})) {	# ak este nie je loaded
					push @{$leases->{$MAC}}, $1;
				}
			} elsif ($state eq 'free') {
				# if free, ignore
			} else {
				push  @{$strange_leases}, "MAC=$MAC/IP=$1/state=$state";	# najdi podozrive
			}
                } elsif ($line =~ /binding state (.*);/) {		# parse state - read second
                        $state = $1;
                } elsif ($line =~ /hardware ethernet (.*);/) {		# parse MAC - read first
                        $MAC = $1;
                } else {

                }
        }
	$self->{'{leases}'} = $leases;			# pripoj lease k predoslym
	$self->{'{reverse_leases}'} = $reverse_leases;	# pripoj reverznu lease k predoslym
	$self->{'{strange_leases}'} = $strange_leases;	# pripoj podozrive lease k predoslym
}

sub refresh {

# vygeneruje konfigurak pre DHCP server, ktory moze obsluhovat viacero LAN segmentov / subnetov

	my $self = shift;
	my $dhcpdconf = "# Tento subor bol vygenerovany ".Utils::current_time_slovak()." systemom LANmgmt.\n";
	$dhcpdconf .= "\n### 1 / 2    DEFINICIE SUBNETOV    ######################\n\n";
	my $dhcpdconf_hosts;	# hostov pricapit naspodok
	my $MAC2IPs;
	foreach my $segment ($self->segments) { # vygeneruj cast konfiguracie pre kazdy segment
		my $subnet = $segment->subnet;
		warn "Generujem globalne parametre dhcpd.conf pre $subnet . . .\n"; my $time_before = time;

		$subnet = NetAddr::IP->new($subnet);
	#	if ($subnet eq undef) {
	#		next;
	#	}
		my $NETWORK = $subnet->addr;
		my $NETWORK_FULL = $segment->subnet;
		my $NAME = $segment->name;
		my $NOTICE = $segment->notice;
		my $NETMASK = $subnet->mask();
		my $BROADCAST = $subnet->broadcast()->addr;
		my $GATEWAY = $segment->default_gateway;
		my $NTP = $segment->ntp;
		my $DOMAIN = $segment->domain;
		my $DNS = join ", ", ($segment->dns1,$segment->dns2); # v databaze je aj dns3 a dns4 
		my $WINS = $segment->wins || 'nowins.uniba.sk';
		my $LEASE_TIME_UNREGISTERED = $segment->dhcp_lease_time;
		my $first_usable = $subnet->first->addr; my $last_usable = $subnet->last->addr;

		my $action_when_unknown;
		if ($segment->public_dhcp) {
			$action_when_unknown = 'allow';
		} else {
			$action_when_unknown = 'deny';
		}

		$dhcpdconf .= "subnet $NETWORK netmask $NETMASK {\
# $NAME ($NOTICE)\n
	        option subnet-mask              $NETMASK;\
        	option broadcast-address        $BROADCAST;\
	        option routers                  $GATEWAY;\
        	option domain-name              \"$DOMAIN\";\
	        option domain-name-servers      $DNS;\
		option ntp-servers		$NTP;\
		option netbios-name-servers	$WINS;\
		option netbios-node-type	8;	# man dhcp-options\
		option smtp-server		smtp.$DOMAIN;
\
	        default-lease-time              $LEASE_TIME_UNREGISTERED;\
	        max-lease-time                  $LEASE_TIME_UNREGISTERED;\
\
		$action_when_unknown unknown-clients;
\
		ddns-domainname \"$DOMAIN\";\
";
		warn "Generovanie globalnych parametrov skoncene. Operacia trvala ".(time - $time_before)." sekund.\n";
		warn "Generujem pooly dhcpd.conf pre $subnet . . .\n"; $time_before = time;

		# vyberie z databazy tie adresy, ktore patria do daneho subnetu:
		my @hosts = Host->retrieve_from_sql("ip << '$NETWORK_FULL' ORDER BY IP asc");

		# zaciatok a koniec definicie poolu:
		my $BEGIN = "\tpool { allow all clients; range ";
		my $BEGIN_RESERVED = "\tpool { deny unknown-clients; range ";
		my $END = ";}\n";

		# deklaracie premennych
		my $previous_was_pool;
		my $prevIP = ""; my $curIP;

		my $dhcpdconf_pools;
		# pre kazdu IPcku zo subnetu rob nasledovne:
		foreach my $host (@hosts) {
			$curIP = $host->ip;
			my $MAC = $host->mac;
			if ($MAC) {
				push @{$MAC2IPs->{lc($MAC)}},{ip => $curIP,
								comment => $host->notice,
								hostname => $host->hostname};
			}

			# poolova IPcka nesmie mat uvedenu MAC adresu
			my $isnotpool = $host->mac ? 1 : 0;

			# poolova IPcka musi byt explicitne oznacena flagom is_pool
			$isnotpool += $host->is_pool ? 0 : 1;

			# pri generovani poolov budeme ignorovat aj broadcast, network a default_gateway
			if ($curIP eq $subnet->broadcast->addr || $curIP eq $subnet->network->addr) {
				next;
			}
			if (!$isnotpool && ($curIP ne $segment->default_gateway)) {#
				if (!defined $prevIP or !$prevIP) {
					# sme na zaciatku poolu a hned prva IP je poolova
					# otvor pool, ale predosly nezatvaraj	
					$dhcpdconf_pools .= $BEGIN.$curIP;
				} else {
					# nie sme na zaciatku poolu a je to poolova IPcka
					if ($previous_was_pool) {
						# nie sme na zaciatku, poolova IPcka, aj predtym bola
						# do nothing!
					} else {
						# nie sme na zaciatku, poolova, ale predtym nebola
						# zacni novy pool
						$dhcpdconf_pools .= " ".$prevIP.$END.$BEGIN.$curIP;
					}
				}
				$previous_was_pool = 1;
			} else {				# not pool
				if (!defined $prevIP or !$prevIP) {
					# sme na zaciatku, ipcka nie je poolova
					$dhcpdconf_pools .= $BEGIN_RESERVED.$curIP;
				} else {
					# nie sme na zaciatku, ipcka nie je poolova
					if ($previous_was_pool) {
						# nie sme na zaciatku, poolova nie je, ale predosla bola
						# zavri pool predoslou ipckou
						$dhcpdconf_pools .= " ".$prevIP.$END.$BEGIN_RESERVED.$curIP;
					} else {
						# nie sme na zac., poolova nie je, predosla tiez nebola
						# do nothing!
					}
				}
				$previous_was_pool = 0;
			}
			$prevIP = $curIP;
		}

		# koniec definicie posledneho poolu
#		if ($previous_was_pool) {
		if (defined $dhcpdconf_pools) {
			$dhcpdconf_pools .= " ".$prevIP.$END;
		}
		$dhcpdconf .= $dhcpdconf_pools;
#		} else {
#			$dhcpdconf .= " ".$prevIP.$END;
#		}
		
		# koniec definicie subnetu v konfiguraku
		$dhcpdconf .= "\n\n} #subnet $NETWORK/$NETMASK\n\n";
		$prevIP = 0;

		warn "Generovanie poolov skoncene. Operacia trvala ".(time - $time_before)." sekund.\n";
		warn "Generujem hosty dhcpd.conf pre $subnet . . .\n"; $time_before = time;

		# inicializacia premennej
		my $oldMAC = 0;


#		# vytiahnutie z db vsetky IPciek daneho subnetu (TODO: optimalizacia - robit to len raz, ale pozor na zoradenie!)
#		@hosts = Host->retrieve_from_sql("ip << '$NETWORK_FULL' AND mac is not null ORDER BY mac");	

#        	my $i = 0;
#		foreach my $host (@hosts) { 
 #       		my $IP = $host->ip; #mac, hostname
#			my $notice = $host->notice;
#			my $MAC = $host->mac;
#
#			if ($MAC eq $oldMAC) {	# zoradili sme podla MAC, aby sa nam lahsie robili host zaznamy pre multihomed
#				$dhcpdconf_hosts .= ", $IP";
#			} else {
#				my ($begin, $end);	# zaciatok a koniec host statementu
#				$end = "\n";
#				$begin = "# $notice\nhost host$i {hardware ethernet $MAC; fixed-address $IP"; $i++;
#				if ($oldMAC) {	# kedze sme selectli mac is not null, tak toto nenastane len pri prvej adrese
#					$end = $END;
#				} else {
#
#				}
#				$dhcpdconf_hosts .= $end.$begin;
#			}		
#			$oldMAC = $MAC;
#		} # foreach host
#		if ($#hosts > 0) {
#			$dhcpdconf_hosts .= $END;
#		}
		warn "Generovanie hostov skoncene. Operacia trvala ".(time - $time_before)." sekund.\n";
	} # foreach segment

	# v cykle prechadzanie IPciek a generovanie host zaznamov do dhcpd.conf
	foreach my $MAC (keys %{$MAC2IPs}) {
		my $MACdec = Utils::MAChex2dec($MAC);    # prevedenie MAC z hexa do decimal tvaru
		my $comments = " ";                            # komentare pre jednotlive IPcky tej istej MAC
		my @IPs;
		# pre kazdy zaznam jednotlivych MAC rob nasledovne:
		foreach my $IPrecord (@{$MAC2IPs->{$MAC}}) {
			# gererovanie komentara do konfiguraku
	                $comments .= "# ";
			$comments .= $IPrecord->{ip}." ";
			if ($IPrecord->{hostname}) { $comments .= $IPrecord->{hostname}." "; }
			if ($IPrecord->{comment}) { $comments .= $IPrecord->{comment}."\n\t"; }
        	        push @IPs, $IPrecord->{ip};
		}
		my $IPs = join ', ', @IPs;
		$dhcpdconf_hosts .= "#================================================================\
host NIC.$MACdec {\
	#---------------------------------------------------------\
	${comments}\
	#---------------------------------------------------------\
	hardware ethernet $MAC;\n\tfixed-address $IPs; }\n\n";
	}

	# vytvorenie docasneho suboru, ktory bude nasledne kopirovany na prislusny DHCP server
	my $tmp_conf_file = '/tmp/dhcpd.conf.'.$self->dhcp_serverid;
        open (DHCPDCONF, ">$tmp_conf_file");
        print DHCPDCONF $dhcpdconf."\n\n### 2 / 2    DEFINICIE HOSTOV    ######################\n\n".$dhcpdconf_hosts;
        close (DHCPDCONF);

	# kopirovanie noveho konfiguraku na DHCP server a restart DHCP servera
	$self->send_file($tmp_conf_file, $self->conf_file_path); 
       return $self->command($self->dhcp_reload_cmd);
}

sub configure_dhcp_detector {       # detekuje, ci bezi spravne DHCP na danom LAN segmente
        my $self = shift;
	my $fqdn = $self->fqdn;
	my $admin_email = $self->admin_email;

	my @segments = $self->segments;
	my $gateways;	# TODO: my $dhcpdetectors;

	my $time_before;

	warn "Generujem dhcpdetector.sh pre vsetky subnety DHCP servera $fqdn, ktory ma v sprave $admin_email . . .\n"; $time_before = time;
	foreach my $segment (@segments) {
        	my $subnet = $segment->subnet;
        	my $subnet_name = $segment->name;
        	my $subnet_description = $segment->notice;

        	# aktualizacia skriptov na detekciu DHCP serverov
	        my $conf = Conf->show;
	        my $script_dir = $conf->{DHCP_DETECT_DIR};

	        my $gw = $segment->gatewayid;
		$gw->send_file($script_dir."/*", "/home/".$gw->default_user."/roguedetect/");
	        # najdenie vhodnej MAC adresy pre test
        	my @hosts = Host->retrieve_from_sql(qq{ ip << '$subnet' AND mac IS NOT NULL ORDER BY ip DESC});
        	my $MAC;
        	if ($#hosts < 0) {
                	$MAC = '00:11:22:33:44:55';
        	} else {
                	my $random_host = $hosts[0];	# vytiahnutie poslednej uplnej dvojice MAC/IP z db
                	$MAC = $random_host->mac;
        	}

        	# generovanie prikazov
        	my $command = "/home/lanmgmt/roguedetect/dhcpdetector.pl --sname=\"$subnet_name\" --iface ".$segment->ids_iface.'.'.$segment->segmentid." --sdesc=\"$subnet_description\" --recipient ".$segment->admin_email.",".$self->admin_email." --verbose --mac $MAC --subnet=$subnet --approved=".$segment->default_gateway;
		$gateways->{$gw->gatewayid} .= "\n".$command."\n";
#        	$gw->command("$command");
#        	print $command;
	}
	warn "Generovanie dhcpdetector.sh skoncene. Operacia trvala ".(time - $time_before)." sekund.\n";
	warn "Kopirujem instancie dhcpdetector.sh pre vsetky subnety DHCP servera $fqdn na prislusne firewally . . .\n"; $time_before = time;

	foreach my $gatewayid (keys %{$gateways}) {
		my $local_tmp_file = "/tmp/dhcpdetector.sh.$gatewayid";
		open DHCPDETECTOR, ">$local_tmp_file";
		print DHCPDETECTOR "#!/bin/bash\n\n# Tento skript bol vygenerovany ".Utils::current_time_slovak()." systemom LANmgmt.\n# Na ucely zistenia aktualnych casov, v ktorych je skript spustany cronom, alebo ich zmeny, kontaktujte spravcu systemu.\n\ncd /home/lanmgmt/roguedetect\n".$gateways->{$gatewayid}."\n";
		close DHCPDETECTOR;		
		my $gw = Gateway->retrieve($gatewayid);
	        $gw->send_file($local_tmp_file, "/home/".$gw->default_user."/dhcpdetector.sh");
	}
	warn "Kopirovanie dhcpdetector.sh skoncene. Operacia trvala ".(time - $time_before)." sekund.\n";

}


1;

